package main

import (
	"database/sql"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"strconv"
	"strings"
)

type DefaultModel struct {
	db     *sql.DB
	prefix string
}

func (this *DefaultModel) Init() error {
	this.prefix = dbCfg.DbPrefix
	var err error
	this.db, err = sql.Open("mysql", dbCfg.DbUsername+":"+dbCfg.DbPassword+"@"+dbCfg.DbAddress+"/"+dbCfg.DbName+"?charset=utf8")
	if err != nil {
		return err
	}
	//create table if not exist
	this.db.Exec("CREATE TABLE IF NOT EXISTS `" + this.prefix + "config` (`Name` varchar(255) NOT NULL, `Value` text NOT NULL, PRIMARY KEY (`Name`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;")
	this.db.Exec("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plugins` (`Name` varchar(255) NOT NULL, `Enabled` int(11) NOT NULL, `Config` text NOT NULL, PRIMARY KEY (`Name`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;")
	this.db.Exec("CREATE TABLE IF NOT EXISTS `" + this.prefix + "posts` (`Pid` int(11) NOT NULL AUTO_INCREMENT, `Title` varchar(255) NOT NULL, `Content` text NOT NULL, `Created` bigint(20) NOT NULL, `Modified` bigint(20) NOT NULL, PRIMARY KEY (`Pid`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;")
	this.db.Exec("CREATE TABLE IF NOT EXISTS `" + this.prefix + "tags` (`Pid` int(11) NOT NULL, `TagName` varchar(255) NOT NULL, KEY `Pid` (`Pid`), KEY `TagName` (`TagName`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;")
	return nil
}

func (this *DefaultModel) Close() {
	this.db.Close()
}

func (this *DefaultModel) Sql() *sql.DB {
	return this.db
}

func (this *DefaultModel) GetAllPosts() ([]*PostData, error) {
	result := []*PostData{}
	rows, err := this.db.Query("SELECT * FROM " + this.prefix + "posts ORDER BY Pid DESC")
	if err != nil {
		return result, err
	}
	for rows.Next() {
		data := new(PostData)
		err := rows.Scan(
			&data.Pid,
			&data.Title,
			&data.Content,
			&data.Created,
			&data.Modified,
		)
		if err != nil {
			continue
		}
		result = append(result, data)
	}

	tags := make(map[int][]string)
	tagRows, err := this.db.Query("SELECT * FROM " + this.prefix + "tags")
	if err == nil {
		for tagRows.Next() {
			var pid int
			var tag string
			err := tagRows.Scan(&pid, &tag)
			if err != nil {
				continue
			}
			tags[pid] = append(tags[pid], tag)
		}
	}
	if len(tags) > 0 {
		for _, data := range result {
			if t, ok := tags[data.Pid]; ok {
				data.Tags = t
			}
		}
	}
	return result, nil
}

func (this *DefaultModel) GetPosts(page int) ([]*PostData, error) {
	npp := cfg.NumPerPage
	if page < 1 {
		page = 1
	}
	result := []*PostData{}
	rows, err := this.db.Query("SELECT * FROM "+this.prefix+"posts ORDER BY Pid DESC LIMIT ? OFFSET ?", npp, (page-1)*npp)
	if err != nil {
		return result, err
	}
	pids := []string{}
	for rows.Next() {
		data := new(PostData)
		err := rows.Scan(
			&data.Pid,
			&data.Title,
			&data.Content,
			&data.Created,
			&data.Modified,
		)
		if err != nil {
			continue
		}
		pids = append(pids, strconv.Itoa(data.Pid))
		result = append(result, data)
	}

	tags := make(map[int][]string)
	tagRows, err := this.db.Query("SELECT * FROM " + this.prefix + "tags WHERE Pid IN (" + strings.Join(pids, ",") + ")")
	if err == nil {
		for tagRows.Next() {
			var pid int
			var tag string
			err := tagRows.Scan(&pid, &tag)
			if err != nil {
				continue
			}
			tags[pid] = append(tags[pid], tag)
		}
	}
	if len(tags) > 0 {
		for _, data := range result {
			if t, ok := tags[data.Pid]; ok {
				data.Tags = t
			}
		}
	}
	return result, nil
}

func (this *DefaultModel) GetPostsCnt() (int, error) {
	row := this.db.QueryRow("SELECT COUNT(Pid) AS Cnt FROM " + this.prefix + "posts")
	var cnt int = 0
	err := row.Scan(&cnt)
	return cnt, err
}

func (this *DefaultModel) GetPostsByTag(tag string, page int) ([]*PostData, error) {
	npp := cfg.NumPerPage
	if page < 1 {
		page = 1
	}
	result := []*PostData{}
	rows, err := this.db.Query("SELECT * FROM "+this.prefix+"posts WHERE Pid IN (SELECT Pid From "+this.prefix+"tags WHERE TagName=?) ORDER BY Pid DESC LIMIT ? OFFSET ?", tag, npp, (page-1)*npp)
	if err != nil {
		return result, err
	}
	pids := []string{}
	for rows.Next() {
		data := new(PostData)
		err := rows.Scan(
			&data.Pid,
			&data.Title,
			&data.Content,
			&data.Created,
			&data.Modified,
		)
		if err != nil {
			continue
		}
		pids = append(pids, strconv.Itoa(data.Pid))
		result = append(result, data)
	}

	tags := make(map[int][]string)
	tagRows, err := this.db.Query("SELECT * FROM " + this.prefix + "tags WHERE Pid IN (" + strings.Join(pids, ",") + ")")
	if err == nil {
		for tagRows.Next() {
			var pid int
			var tagname string
			err := tagRows.Scan(&pid, &tagname)
			if err != nil {
				continue
			}
			tags[pid] = append(tags[pid], tagname)
		}
	}
	if len(tags) > 0 {
		for _, data := range result {
			if t, ok := tags[data.Pid]; ok {
				data.Tags = t
			}
		}
	}
	return result, nil
}

func (this *DefaultModel) GetPostsCntByTag(tag string) (int, error) {
	row := this.db.QueryRow("SELECT COUNT(Pid) AS Cnt FROM "+this.prefix+"tags WHERE TagName=?", tag)
	var cnt int = 0
	err := row.Scan(&cnt)
	return cnt, err
}

func (this *DefaultModel) GetPostById(id int) (*PostData, error) {
	row := this.db.QueryRow("SELECT * FROM "+this.prefix+"posts WHERE Pid=?", id)
	data := new(PostData)
	err := row.Scan(
		&data.Pid,
		&data.Title,
		&data.Content,
		&data.Created,
		&data.Modified,
	)
	if err != nil {
		return nil, err
	}
	tagRows, err := this.db.Query("SELECT TagName FROM "+this.prefix+"tags WHERE Pid=?", id)
	if err == nil {
		for tagRows.Next() {
			var tagname string
			err := tagRows.Scan(&tagname)
			if err != nil {
				continue
			}
			data.Tags = append(data.Tags, tagname)
		}
	}
	return data, nil
}

func (this *DefaultModel) GetAllTags() ([]*TagData, error) {
	result := []*TagData{}
	rows, err := this.db.Query("SELECT TagName, COUNT(TagName) AS Cnt FROM " + this.prefix + "tags GROUP BY TagName ORDER BY Cnt DESC")
	if err != nil {
		return result, err
	}
	for rows.Next() {
		data := new(TagData)
		err := rows.Scan(&data.TagName, &data.PostCnt)
		if err != nil {
			continue
		}
		result = append(result, data)
	}
	return result, nil
}

func (this *DefaultModel) SetTag(name, newname string) error {
	_, err := this.db.Exec("UPDATE "+this.prefix+"tags SET TagName=? WHERE TagName=?", newname, name)
	return err
}

func (this *DefaultModel) GetAllPlugins() ([]*PluginData, error) {
	result := []*PluginData{}
	rows, err := this.db.Query("SELECT * FROM " + this.prefix + "plugins")
	if err != nil {
		return result, err
	}
	for rows.Next() {
		data := new(PluginData)
		err := rows.Scan(&data.Name, &data.Enabled, &data.Data)
		if err != nil {
			continue
		}
		result = append(result, data)
	}
	return result, nil
}

func (this *DefaultModel) GetPluginByName(name string) (*PluginData, error) {
	row := this.db.QueryRow("SELECT * FROM "+this.prefix+"plugins WHERE Name=?", name)
	data := new(PluginData)
	err := row.Scan(
		&data.Name,
		&data.Enabled,
		&data.Data,
	)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (this *DefaultModel) SavePlugin(data *PluginData) error {
	result, err := this.db.Exec("UPDATE "+this.prefix+"plugins SET Enabled=?,Data=? WHERE Name=?", data.Enabled, data.Data, data.Name)
	if err != nil {
		return err
	}
	if i, _ := result.RowsAffected(); i == 0 {
		_, err := this.db.Exec("INSERT INTO "+this.prefix+"plugins (Name,Enabled,Data) VALUES (?,?,?)", data.Name, data.Enabled, data.Data)
		if err != nil {
			return err
		}
	}
	return nil
}

func (this *DefaultModel) SetPluginData(name, data string) error {
	_, err := this.db.Exec("UPDATE "+this.prefix+"plugins SET Data=? WHERE Name=?", data, name)
	if err != nil {
		return err
	}
	return nil
}

func (this *DefaultModel) SavePost(data *PostData) error {
	if data.Pid > 0 {
		result, err := this.db.Exec("UPDATE "+this.prefix+"posts SET Title=?,Content=?,Modified=? WHERE Pid=?", data.Title, data.Content, data.Modified, data.Pid)
		if err != nil {
			return err
		}
		if rs, e := result.RowsAffected(); e != nil || rs == 0 {
			return errors.New("post not saved")
		}
		this.db.Exec("DELETE FROM "+this.prefix+"tags WHERE Pid=?", data.Pid)
	} else {
		result, err := this.db.Exec("INSERT INTO "+this.prefix+"posts (Title,Content,Created,Modified) VALUES (?,?,?,?,?)", data.Title, data.Content, data.Created, data.Modified)
		if err != nil {
			return err
		}
		id, e := result.LastInsertId()
		if e != nil || id == 0 {
			return errors.New("post not saved")
		}
		data.Pid = int(id)
	}
	for _, tag := range data.Tags {
		this.db.Exec("INSERT INTO "+this.prefix+"tags (TagName,Pid) VALUES (?,?)", tag, data.Pid)
	}
	return nil
}

func (this *DefaultModel) GetAllConfig() (map[string]string, error) {
	result := make(map[string]string)
	rows, err := this.db.Query("SELECT Name, Value FROM " + this.prefix + "config")
	if err != nil {
		return result, err
	}
	for rows.Next() {
		var k, v string
		err := rows.Scan(&k, &v)
		if err != nil {
			continue
		}
		result[k] = v
	}
	return result, nil
}

func (this *DefaultModel) GetConfig(name string) (string, error) {
	row := this.db.QueryRow("SELECT Value FROM "+this.prefix+"config WHERE Name=?", name)
	var v string
	err := row.Scan(&v)
	if err != nil {
		return "", err
	}
	return v, nil
}

func (this *DefaultModel) SetConfig(name string, value string) error {
	result, err := this.db.Exec("UPDATE "+this.prefix+"config SET Value=? WHERE Name=?", value, name)
	if err != nil {
		return err
	}
	if i, _ := result.RowsAffected(); i == 0 {
		_, err := this.db.Exec("INSERT INTO "+this.prefix+"config (Name,Value) VALUES (?,?)", name, value)
		if err != nil {
			return err
		}
	}
	return nil
}
